﻿using System;
using System.Globalization;

namespace DM4Script
{
    public partial class ScriptParse
    {
        /// <summary>
        /// variables for positioning
        /// </summary>
        private int _numberCurrentSymbol;
        private int _numberSymbolStart;
        private int _numberLine = 1;
        private int _tokenNumSymbolInLineStart;
        private int _tokenNumSymbolInLineEnd;

        /// <summary>
        /// CurTok - is the current
        /// </summary>
        private char _curTok;

        /// <summary>
        /// tokens
        /// </summary>
        private string _identifierStr;  
        private string _propertyStr;    
        private double _numVal;         
        
        private char _lastChar = ' ';

        /// <summary>
        /// reads char from script
        /// </summary>
        /// <returns>char</returns>
        private char Getchar()
        {
            char returnChar = ScriptTxt.Length > _numberCurrentSymbol ? ScriptTxt[_numberCurrentSymbol++] : '\0';

            ++_tokenNumSymbolInLineEnd;
            if (returnChar == '\n')
            {
                ++_numberLine;
                _tokenNumSymbolInLineEnd = 0;
            }

            return returnChar;
        }

        /// <summary>
        /// returns the next token
        /// </summary>
        /// <returns>token</returns>
        private char GetTok()
        {
            // Пропускаем пробелы.
            while (char.IsWhiteSpace(_lastChar))
            {
                _numberSymbolStart = _numberCurrentSymbol;
                _tokenNumSymbolInLineStart = _tokenNumSymbolInLineEnd;

                _lastChar = Getchar();
            }

            if (IsLetter(_lastChar))
            {
                // идентификатор: [a-zA-Z][a-zA-Z0-9]*
                _identifierStr = _lastChar.ToString(CultureInfo.InvariantCulture);
                while (IsLetterOrDigit((_lastChar = Getchar())))
                    _identifierStr += _lastChar;

                if (_lastChar == '.') //проверка на tok_property
                {
                    // В противном случае просто возвращаем символ как значение ASCII
                    char thisCharPoint = _lastChar;
                    if (!IsLetter(_lastChar = Getchar())) return thisCharPoint; //заглядываем на один символ вперед

                    // метод: [a-zA-Z][a-zA-Z0-9]*
                    _propertyStr = _lastChar.ToString(CultureInfo.InvariantCulture);
                    while (IsLetterOrDigit((_lastChar = Getchar())))
                        _propertyStr += _lastChar;

                    _propertyStr = _propertyStr.ToLowerInvariant();

                    return (char)Token.Property;
                }

                _identifierStr = _identifierStr.ToLowerInvariant();
                if (_identifierStr == Token.Def.ToString().ToLowerInvariant()) return (char)Token.Def;
                if (_identifierStr == Token.Extern.ToString().ToLowerInvariant()) return (char)Token.Extern;

                if (_identifierStr == Token.Declare.ToString().ToLowerInvariant()) return (char)Token.Declare;
                if (_identifierStr == Token.Task.ToString().ToLowerInvariant()) return (char)Token.Task;
                if (_identifierStr == Token.SubTask.ToString().ToLowerInvariant()) return (char)Token.SubTask;
                if (_identifierStr == Token.Run.ToString().ToLowerInvariant()) return (char)Token.Run;
                if (_identifierStr == Token.Module.ToString().ToLowerInvariant()) return (char)Token.Module;
                if (_identifierStr == Token.SessionCount.ToString().ToLowerInvariant()) return (char)Token.SessionCount;
                if (_identifierStr == Token.Priority.ToString().ToLowerInvariant()) return (char)Token.Priority;
                if (_identifierStr == Token.IgnoreErrors.ToString().ToLowerInvariant()) return (char)Token.IgnoreErrors;
                if (_identifierStr == "on") return (char)Token.RunTask;

                if (_identifierStr == Token.If.ToString().ToLowerInvariant()) return (char)Token.If;
                if (_identifierStr == Token.Then.ToString().ToLowerInvariant()) return (char)Token.Then;
                if (_identifierStr == Token.Else.ToString().ToLowerInvariant()) return (char)Token.Else;
                if (_identifierStr == Token.ElseIf.ToString().ToLowerInvariant()) return (char)Token.ElseIf;

                if (_identifierStr == "and") return (char)Token.Oper;
                if (_identifierStr == "or") return (char)Token.Oper;

                if (_identifierStr == "true") return (char)Token.Bool;
                if (_identifierStr == "false") return (char)Token.Bool;
                if (_identifierStr == "t") return (char)Token.Bool;
                if (_identifierStr == "f") return (char)Token.Bool;

                _propertyStr = null;
                return (char)Token.Identifier;
            }

            if (char.IsNumber(_lastChar) || _lastChar == '.')
            {
                // Число: [0-9.]+
                string numStr = "";
                do
                {
                    numStr += _lastChar == '.' ? ',' : _lastChar;
                    _lastChar = Getchar();
                } while (char.IsNumber(_lastChar) || _lastChar == '.');

                _numVal = Convert.ToDouble(numStr);
                return (char)Token.Number;
            }

            if (_lastChar == '#')
            {
                // Комментарий до конца строки
                do _lastChar = Getchar(); while (_lastChar != '\0' && _lastChar != '\n' && _lastChar != '\r');

                if (_lastChar != '\0')
                    return GetTok();
            }

            // Проверка конца файла.
            if (_lastChar == '\0')
                return (char)Token.Eof;

            // В противном случае просто возвращаем символ как значение ASCII
            char thisChar = _lastChar;
            _lastChar = Getchar();

            //обработка коментария '--' комментарий до конца строки
            if (thisChar.ToString(CultureInfo.InvariantCulture) + _lastChar.ToString(CultureInfo.InvariantCulture) ==
                "--")
            {
                do _lastChar = Getchar(); while (_lastChar != '\0' && _lastChar != '\n' && _lastChar != '\r');

                if (_lastChar != '\0')
                {
                    _lastChar = Getchar();
                    return GetTok();
                }
                if (_lastChar == '\0') return (char)Token.Eof;
            }

            //обработка коментария '/*' комментарий до конца '*/'
            if (thisChar.ToString(CultureInfo.InvariantCulture) + _lastChar.ToString(CultureInfo.InvariantCulture) ==
                "/*")
            {
                while (true)
                {
                    string rem = "";
                    do
                    {
                        _lastChar = Getchar();
                    } while (_lastChar != '\0' && _lastChar != '*');

                    if (_lastChar == '\0') return (char)Token.Eof;

                    rem += _lastChar;
                    _lastChar = Getchar();
                    if (rem + _lastChar == "*/") break;
                }

                if (_lastChar != '\0')
                {
                    _lastChar = Getchar();
                    return GetTok();
                }
            }

            //обработка бинарных операторов
            {
                string testOper = thisChar.ToString(CultureInfo.InvariantCulture) +
                                  _lastChar.ToString(CultureInfo.InvariantCulture);
                if (Setup.binopPrecedence.ContainsKey(testOper))
                {
                    _identifierStr = testOper;
                    _lastChar = Getchar();
                    return (char)Token.Oper;
                }

                if (Setup.binopPrecedence.ContainsKey(thisChar.ToString(CultureInfo.InvariantCulture)))
                {
                    _identifierStr = thisChar.ToString(CultureInfo.InvariantCulture);
                    return (char) Token.Oper;
                }
            }


            return thisChar;
        }

        /// <summary>
        /// the next token
        /// </summary>
        /// <returns>token</returns>
        private char GetNextToken()
        {
            _numberSymbolStart = _numberCurrentSymbol - 1;
            _tokenNumSymbolInLineStart = _tokenNumSymbolInLineEnd;
            return _curTok = GetTok();
        }

        private bool IsLetterOrDigit(char val)
        {
            return char.IsLetterOrDigit(val) || val == '_';
        }

        private bool IsLetter(char val)
        {
            return char.IsLetter(val) || val == '_';
        }
    }
}
